mod scripts;

use clap::Parser;
use crossterm::{
    event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
    execute,
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use env_logger::{Builder, Target};
use log::{error, info, trace, warn};
use ratatui::{
    backend::{Backend, CrosstermBackend},
    layout::{Constraint, Layout, Rect},
    style::{self, Color, Modifier, Style, Stylize},
    terminal::{Frame, Terminal},
    text::Text,
    widgets::{
        Block, BorderType, Cell, Clear, HighlightSpacing, Paragraph, Row, Scrollbar,
        ScrollbarOrientation, ScrollbarState, Table, TableState, Wrap,
    },
};
use serde::{Deserialize, Serialize};
use std::{error::Error, io};
use style::palette::tailwind;
use tokio::{
    fs::File,
    io::{AsyncReadExt, AsyncWriteExt},
    net::TcpStream,
    select,
    sync::mpsc::{self, Receiver, Sender},
    time::{sleep, Duration},
};
use tui_textarea::{Input, Key, TextArea, CursorMove};

const ITEM_HEIGHT: u16 = 1;

#[derive(clap::ValueEnum, Clone, Debug, Serialize)]
enum COLORS {
    NEUTRAL,
    BLUE,
    EMERALD,
    INDIGO,
    RED,
}

impl COLORS {
    fn to_tailwind(self) -> tailwind::Palette {
        match self {
            COLORS::NEUTRAL => tailwind::NEUTRAL,
            COLORS::BLUE => tailwind::BLUE,
            COLORS::EMERALD => tailwind::EMERALD,
            COLORS::INDIGO => tailwind::INDIGO,
            COLORS::RED => tailwind::RED,
        }
    }
}

#[derive(Parser)]
#[command(name = "spi-gui internal cmd")]
#[allow(non_camel_case_types)]
struct COMMAND_PARSE {
    cmd: COMMAND,
    rcmd: Option<String>,
    #[arg(short, long)]
    bits: Option<String>,
    #[arg(short, long)]
    path: Option<String>,
    #[arg(short, long)]
    addr: Option<u8>,
    #[arg(short, long)]
    data: Option<u8>,
}

#[derive(clap::ValueEnum, Clone, Debug, Serialize)]
enum COMMAND {
    HELP,
    WRITE_BIT,
    LOAD,
    GENERATE,
    RESET,
    RCMD,
    SAVE
}

#[derive(Parser)]
#[command(name = "spi-gui")]
#[command(author = "yezoli")]
#[command(version = "1.0")]
struct Cli {
    #[arg(short, long)]
    color: Option<COLORS>,
    #[arg(short, long)]
    periodic_update: Option<bool>,
}

#[derive(Serialize, Deserialize)]
struct Packet {
    cmd: String,
    status: String,
    payload: Vec<u8>,
}

#[derive(Serialize, Deserialize)]
struct PacketStr {
    cmd: String,
    status: String,
    payload: String,
}

#[derive(Serialize, Deserialize)]
struct PacketBits {
    cmd: String,
    status: String,
    bits: String,
    data: u8,
}

struct TableColors {
    buffer_bg: Color,
    header_bg: Color,
    header_fg: Color,
    row_fg: Color,
    selected_style_fg: Color,
    normal_row_color: Color,
    alt_row_color: Color,
    footer_border_color: Color,
}

impl TableColors {
    const fn new(color: &tailwind::Palette) -> Self {
        Self {
            buffer_bg: tailwind::SLATE.c950,
            header_bg: color.c900,
            header_fg: tailwind::SLATE.c200,
            row_fg: tailwind::SLATE.c200,
            selected_style_fg: color.c400,
            normal_row_color: tailwind::SLATE.c950,
            alt_row_color: tailwind::SLATE.c900,
            footer_border_color: color.c400,
        }
    }
}

struct EditStr {
    edit: bool,
    data: String,
}

struct EditStrs {
    edit: bool,
    data: Vec<String>,
}

struct App {
    state: TableState,
    log_vertical_scroll_state: ScrollbarState,
    log_vertical_scroll: usize,
    log: EditStrs,
    ip: EditStr,
    script: EditStr,
    edit: EditStr,
    names: [String; 32],
    items: [u8; 32],
    items_r: [u8; 32],
    sel_items: usize,
    colors: TableColors,
}

impl App {
    fn new() -> Self {
        let names = [
            "0X00".to_owned(),
            "0X01".to_owned(),
            "0X02".to_owned(),
            "0X03".to_owned(),
            "0X04".to_owned(),
            "0X05".to_owned(),
            "0X06".to_owned(),
            "0X07".to_owned(),
            "0X08".to_owned(),
            "0X09".to_owned(),
            "0X0A".to_owned(),
            "0X0B".to_owned(),
            "0X0C".to_owned(),
            "0X0D".to_owned(),
            "0X0E".to_owned(),
            "0X0F".to_owned(),
            "0X10".to_owned(),
            "0X11".to_owned(),
            "0X12".to_owned(),
            "0X13".to_owned(),
            "0X14".to_owned(),
            "0X15".to_owned(),
            "0X16".to_owned(),
            "0X17".to_owned(),
            "0X18".to_owned(),
            "0X19".to_owned(),
            "0X1A".to_owned(),
            "0X1B".to_owned(),
            "0X1C".to_owned(),
            "0X1D".to_owned(),
            "0X1E".to_owned(),
            "0X1F".to_owned(),
        ];

        let items = [0; 32];
        let items_r = [0; 32];

        Self {
            names,
            items,
            items_r,
            log_vertical_scroll: 0,
            log_vertical_scroll_state: ScrollbarState::default(),
            log: EditStrs {
                edit: false,
                data: r"SPI-GUI by yezoli.
The software is distributed following GNU General Public License v3.0.
Log:"
                    .split("\n")
                    .map(|x| String::from(x) + "\n")
                    .collect(),
            },
            ip: EditStr {
                edit: false,
                data: "192.168.1.1".to_owned(),
            },
            edit: EditStr {
                edit: false,
                data: String::new(),
            },
            script: EditStr {
                edit: false,
                data: "load -p ./init.txt".to_owned(),
            },
            state: TableState::default().with_selected(0),
            sel_items: 0,
            colors: TableColors::new(&tailwind::NEUTRAL),
        }
    }

    pub fn right(&mut self) {
        let i = match self.state.selected() {
            Some(i) => (i + 8) % 32,
            None => 0,
        };
        self.state.select(Some(i));
        self.sel_items = i;
    }

    pub fn left(&mut self) {
        let i = match self.state.selected() {
            Some(i) => (i + 24) % 32,
            None => 0,
        };
        self.state.select(Some(i));
        self.sel_items = i;
    }

    pub fn up(&mut self) {
        let i = match self.state.selected() {
            Some(i) => (i + 31) % 32,
            None => 0,
        };
        self.state.select(Some(i));
        self.sel_items = i;
    }

    pub fn down(&mut self) {
        let i = match self.state.selected() {
            Some(i) => (i + 1) % 32,
            None => 0,
        };
        self.state.select(Some(i));
        self.sel_items = i;
    }
}

#[derive(Clone)]
enum Signal {
    CONNECT(String),
    READ(u8),
    WRITE(u8, u8, bool),
    READREPLY(u8, u8),
    READREPLYOV(u8, u8),
    SCRIPT(String),
    RCMD(String),
    WRITEBITS(String, u8),
    RESET,
    FLUSH,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let cli = Cli::parse();
    let log_file = Box::new(std::fs::File::create("./spi-gui.log").unwrap());
    let mut builder = Builder::from_default_env();
    builder.target(Target::Pipe(log_file));
    builder.init();

    let (ui_tx, tcp_rx) = mpsc::channel::<Signal>(1024);
    let (tcp_tx, ui_rx) = mpsc::channel::<Signal>(1024);
    let periodic_update = cli.periodic_update.unwrap_or(true);
    tokio::spawn(tcp_top(tcp_tx, tcp_rx, periodic_update));
    ui_top(
        ui_tx,
        ui_rx,
        cli.color.unwrap_or(COLORS::NEUTRAL).to_tailwind(),
    )
    .await?;
    Ok(())
}

async fn ui_top(
    tx: Sender<Signal>,
    rx: Receiver<Signal>,
    color: tailwind::Palette,
) -> Result<(), Box<dyn Error>> {
    // setup terminal
    enable_raw_mode()?;
    let mut stdout = io::stdout();
    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend)?;

    // create app and run it
    let mut app = App::new();
    app.colors = TableColors::new(&color);

    let mut textarea_ip = TextArea::default();
    textarea_ip.set_style(Style::new().fg(app.colors.row_fg).bg(app.colors.buffer_bg));
    textarea_ip.set_block(
        Block::bordered()
            .border_type(BorderType::Double)
            .border_style(Style::new().fg(app.colors.footer_border_color))
            .title("Connect"),
    );
    textarea_ip.insert_str(app.ip.data.clone());
    let mut textarea_edit = TextArea::default();
    textarea_edit.set_style(Style::new().fg(app.colors.row_fg).bg(app.colors.buffer_bg));
    textarea_edit.set_block(
        Block::bordered()
            .border_type(BorderType::Double)
            .border_style(Style::new().fg(app.colors.footer_border_color))
            .title("Edit"),
    );
    let mut textarea_load = TextArea::default();
    textarea_load.set_style(Style::new().fg(app.colors.row_fg).bg(app.colors.buffer_bg));
    textarea_load.set_block(
        Block::bordered()
            .border_type(BorderType::Double)
            .border_style(Style::new().fg(app.colors.footer_border_color))
            .title("Command"),
    );
    textarea_load.insert_str(app.script.data.clone());

    let res = run_app(
        &mut terminal,
        &mut textarea_ip,
        &mut textarea_edit,
        &mut textarea_load,
        tx,
        rx,
        app,
    )
    .await;

    // restore terminal
    disable_raw_mode()?;
    execute!(
        terminal.backend_mut(),
        LeaveAlternateScreen,
        DisableMouseCapture
    )?;
    terminal.show_cursor()?;

    if let Err(err) = res {
        println!("{err:?}");
    }
    Ok(())
}

async fn run_app<B: Backend>(
    terminal: &mut Terminal<B>,
    textarea_ip: &mut TextArea<'_>,
    textarea_edit: &mut TextArea<'_>,
    textarea_load: &mut TextArea<'_>,
    tx: Sender<Signal>,
    mut rx: Receiver<Signal>,
    mut app: App,
) -> Result<(), Box<dyn Error>> {
    loop {
        while !rx.is_empty() {
            if let Some(pack) = rx.recv().await {
                match pack {
                    Signal::READREPLY(addr, data) => {
                        app.items_r[addr as usize] = data;
                    }
                    Signal::READREPLYOV(addr, data) => {
                        app.items_r[addr as usize] = data;
                        app.items[addr as usize] = data;
                    }
                    _ => {}
                }
            }
        }

        terminal.draw(|f| ui(f, &mut app, textarea_ip, textarea_edit, textarea_load))?;

        if let Event::Key(key) = event::read()? {
            if key.kind == KeyEventKind::Press {
                if app.ip.edit {
                    match key.code {
                        KeyCode::Esc => app.ip.edit = false,
                        KeyCode::Enter => {
                            let ip = textarea_ip.lines().iter().next().unwrap().clone();
                            info!("Get ip: {}", &ip);
                            tx.send(Signal::CONNECT(ip.clone())).await?;
                            app.ip.data = ip;
                            app.ip.edit = false;
                        }
                        KeyCode::Backspace => {
                            textarea_ip.input(Input {
                                key: Key::Backspace,
                                ctrl: false,
                                alt: false,
                                shift: false,
                            });
                        }
                        KeyCode::Char(to_insert) => {
                            let input = Input {
                                key: Key::Char(to_insert),
                                ctrl: false,
                                alt: false,
                                shift: false,
                            };
                            textarea_ip.input(input);
                        }
                        KeyCode::Left => {
                            textarea_ip.move_cursor(CursorMove::Back);
                        }
                        KeyCode::Right => {
                            textarea_ip.move_cursor(CursorMove::Forward);
                        }
                        _ => {}
                    }
                } else if app.edit.edit {
                    match key.code {
                        KeyCode::Esc => app.edit.edit = false,
                        KeyCode::Enter => {
                            app.edit.data = textarea_edit.lines().iter().next().unwrap().clone();
                            textarea_edit.delete_line_by_head();
                            if let Ok(data) = app.edit.data.clone().parse() {
                                tx.send(Signal::WRITE(app.sel_items as u8, data, true))
                                    .await?;
                                app.items[app.sel_items] = data;
                                info!("Write {} to Reg {}", data, app.sel_items as u8);
                            } else {
                                warn!("Failed to edit, {} received", app.edit.data.clone());
                            }
                            app.edit.edit = false;
                        }
                        KeyCode::Backspace => {
                            textarea_edit.input(Input {
                                key: Key::Backspace,
                                ctrl: false,
                                alt: false,
                                shift: false,
                            });
                        }
                        KeyCode::Char(to_insert) => {
                            let input = Input {
                                key: Key::Char(to_insert),
                                ctrl: false,
                                alt: false,
                                shift: false,
                            };
                            textarea_edit.input(input);
                        }
                        KeyCode::Left => {
                            textarea_edit.move_cursor(CursorMove::Back);
                        }
                        KeyCode::Right => {
                            textarea_edit.move_cursor(CursorMove::Forward);
                        }
                        _ => {}
                    }
                } else if app.script.edit {
                    match key.code {
                        KeyCode::Esc => app.script.edit = false,
                        KeyCode::Up => {
                            app.log_vertical_scroll = app.log_vertical_scroll.saturating_sub(1);
                            app.log_vertical_scroll_state = app
                                .log_vertical_scroll_state
                                .position(app.log_vertical_scroll);
                        }
                        KeyCode::Down => {
                            app.log_vertical_scroll = app.log_vertical_scroll.saturating_add(1);
                            app.log_vertical_scroll_state = app
                                .log_vertical_scroll_state
                                .position(app.log_vertical_scroll);
                        }
                        KeyCode::Left => {
                            textarea_load.move_cursor(CursorMove::Back);
                        }
                        KeyCode::Right => {
                            textarea_load.move_cursor(CursorMove::Forward);
                        }
                        KeyCode::Enter => {
                            app.script.data = textarea_load.lines().iter().next().unwrap().clone();
                            textarea_load.select_all();
                            textarea_load.delete_char();
                            let mut data = vec!["".to_string()];
                            app.script
                                .data
                                .split_ascii_whitespace()
                                .for_each(|x| data.push(x.to_string()));
                            match COMMAND_PARSE::try_parse_from(data) {
                                Ok(cmd) => match cmd.cmd.clone() {
                                    COMMAND::RESET => {
                                        info!("Get CMD: RESET");
                                        app.log.data.push("Get CMD RESET\n".to_owned());
                                        tx.send(Signal::RESET).await?;
                                    }
                                    COMMAND::RCMD => {
                                        info!("Get CMD: RCMD");
                                        if let Some(rcmd) = cmd.rcmd {
                                            tx.send(Signal::RCMD(rcmd)).await?;
                                        }
                                    }
                                    COMMAND::WRITE_BIT => {
                                        info!("Get CMD: WRITE_BIT");
                                        if let Some(bits) = cmd.bits.clone() {
                                            if let Some(data) = cmd.data.clone() {
                                                tx.send(Signal::WRITEBITS(bits, data)).await?;
                                            }
                                        }
                                    }
                                    COMMAND::HELP => {
                                        info!("Get CMD Help");
                                        app.log.data.push("Get CMD Help\n".to_owned());
                                    }
                                    COMMAND::LOAD => {
                                        info!("Get CMD: LOAD");
                                        if let Some(path) = cmd.path {
                                            match File::open(path).await {
                                                Ok(mut file) => {
                                                    let mut contents = String::new();
                                                    file.read_to_string(&mut contents).await?;
                                                    tx.send(Signal::SCRIPT(contents)).await?;
                                                }
                                                Err(e) => {
                                                    error!("{}, {}", app.script.data.clone(), e)
                                                }
                                            }
                                            app.script.edit = false;
                                        }
                                    }
                                    COMMAND::GENERATE => {
                                        info!("Get CMD: GENERATE");
                                        if let Some(path) = cmd.path {
                                            let t = r"1 0 1
1 1 1
1 2 1
1 3 1
1 4 1
1 5 1
1 6 1
1 7 1
1 8 1
1 9 1
1 10 1
1 11 1
1 12 1
1 13 1
1 14 1
1 15 1
1 16 1
1 17 1
1 18 1
1 19 1
1 20 1
1 21 1
1 22 1
1 23 1
1 24 1
1 25 1
1 26 1
1 27 1
1 28 1
1 29 1
1 30 1
1 31 1
3 0 0
4 0 0";
                                            match File::create(path.clone()).await {
                                                Ok(mut file) => {
                                                    file.write(t.as_bytes()).await?;
                                                }
                                                Err(e) => {
                                                    error!("Failed to generate {}, {}", path, e)
                                                }
                                            }
                                        }
                                    }
                                    COMMAND::SAVE => {
                                        info!("Get CMD: SAVE");
                                        if let Some(path) = cmd.path {
                                            match File::create(path.clone()).await {
                                                Ok(mut file) => {
                                                    let mut data = String::new();
                                                    let regs = app.items_r.clone();
                                                    for i in 0..26 {
                                                        let t = format!("1 {} {} \n", i, regs[i]);
                                                        data += &t;
                                                    }
                                                    data += "3 0 0\n4 0 0";
                                                    file.write(data.as_bytes()).await?;
                                                }
                                                Err(e) => {
                                                    error!("Failed to generate {}, {}", path, e)
                                                }
                                            }
                                        }
                                    }
                                },
                                Err(e) => {
                                    app.log.data.push("Invalid CMD\n".to_owned());
                                    error!("{}", e);
                                }
                            }
                        }
                        KeyCode::Backspace => {
                            textarea_load.input(Input {
                                key: Key::Backspace,
                                ctrl: false,
                                alt: false,
                                shift: false,
                            });
                        }
                        KeyCode::Char(to_insert) => {
                            let input = Input {
                                key: Key::Char(to_insert),
                                ctrl: false,
                                alt: false,
                                shift: false,
                            };
                            textarea_load.input(input);
                        }
                        _ => {}
                    }
                } else if app.log.edit {
                    match key.code {
                        KeyCode::Char('q') | KeyCode::Esc | KeyCode::Enter => app.log.edit = false,
                        KeyCode::Up => {
                            app.log_vertical_scroll = app.log_vertical_scroll.saturating_sub(1);
                            app.log_vertical_scroll_state = app
                                .log_vertical_scroll_state
                                .position(app.log_vertical_scroll);
                        }
                        KeyCode::Down => {
                            app.log_vertical_scroll = app.log_vertical_scroll.saturating_add(1);
                            app.log_vertical_scroll_state = app
                                .log_vertical_scroll_state
                                .position(app.log_vertical_scroll);
                        }
                        _ => {}
                    }
                } else {
                    match key.code {
                        KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
                        KeyCode::Char('s') | KeyCode::Down => app.down(),
                        KeyCode::Char('w') | KeyCode::Up => app.up(),
                        KeyCode::Char('d') | KeyCode::Right => app.right(),
                        KeyCode::Char('a') | KeyCode::Left => app.left(),
                        KeyCode::Char('v') => app.log.edit = true,
                        KeyCode::Char('c') => app.ip.edit = true,
                        KeyCode::Char('l') => app.script.edit = true,
                        KeyCode::Char('r') => tx.send(Signal::READ(app.sel_items as u8)).await?,
                        KeyCode::Char('f') => tx.send(Signal::FLUSH).await?,
                        KeyCode::Char('u') => {
                            let t = app.items_r.clone();
                            for i in (0..32) {
                                tx.send(Signal::READ(i)).await?;
                                sleep(Duration::from_millis(10)).await;
                            };
                            (0..32).for_each(|x| {app.items[x] = t[x];});
                        },
                        KeyCode::Enter => app.edit.edit = true,
                        _ => {}
                    }
                }
            }
        }
        sleep(Duration::from_millis(10)).await;
    }
}

fn ui(
    f: &mut Frame,
    app: &mut App,
    textarea_ip: &mut TextArea,
    textarea_edit: &mut TextArea,
    textarea_load: &mut TextArea,
) {
    let area = f.size();
    let rects = Layout::vertical([Constraint::Min(5), Constraint::Length(4)]).split(area);

    app.log_vertical_scroll_state = app
        .log_vertical_scroll_state
        .content_length(app.log.data.len());

    render_table(f, app, rects[0]);

    render_footer(f, app, rects[1]);

    let (area_script_edit, area_script_log) = v_half_rect(90, 20, 80, area);
    let area_pop = centered_rect(60, 20, area);
    let area_log = centered_rect(90, 80, area);
    if app.ip.edit {
        f.render_widget(Clear, area_pop);
        f.render_widget(textarea_ip.widget(), area_pop);
    } else if app.script.edit {
        f.render_widget(Clear, area_script_edit);
        f.render_widget(textarea_load.widget(), area_script_edit);
        render_log(app, f, &area_script_log);
    } else if app.edit.edit {
        f.render_widget(Clear, area_pop);
        f.render_widget(textarea_edit.widget(), area_pop);
    } else if app.log.edit {
        render_log(app, f, &area_log);
    }
}

fn render_table(f: &mut Frame, app: &mut App, area: Rect) {
    let header_style = Style::default()
        .fg(app.colors.header_fg)
        .bg(app.colors.header_bg);
    let selected_style = Style::default()
        .add_modifier(Modifier::REVERSED)
        .fg(app.colors.selected_style_fg);

    let header = [
        "Address",
        "Data",
        "Data(oct)",
        "Data(bin)",
        "Data(read)",
        "Check",
    ]
    .into_iter()
    .map(Cell::from)
    .collect::<Row>()
    .style(header_style)
    .height(1);
    let mut rows = Vec::new();
    for i in 0..32 {
        let color = match i % 2 {
            0 => app.colors.normal_row_color,
            _ => app.colors.alt_row_color,
        };
        let names = app.names[i].clone();
        let items = app.items[i];
        let items_r = app.items_r[i];
        let row = [
            names,
            format!("{}", items),
            format!("{:#X}", items),
            format!("{:#b}", items),
            format!("{:#X}", items_r),
            if items == items_r {
                "yes".to_owned()
            } else {
                "no".to_owned()
            },
        ];
        rows.push(
            row.iter()
                .map(|x| Cell::from(Text::from(x.clone())))
                .collect::<Row>()
                .style(Style::new().fg(app.colors.row_fg).bg(color))
                .height(ITEM_HEIGHT),
        )
    }
    let bar = " █ ";
    let t = Table::new(
        rows,
        [
            // + 1 is for padding.
            Constraint::Length(15),
            Constraint::Length(15),
            Constraint::Length(15),
            Constraint::Length(15),
            Constraint::Length(15),
            Constraint::Length(15),
            Constraint::Length(15),
            Constraint::Length(15),
            Constraint::Length(15),
            Constraint::Length(15),
        ],
    )
    .header(header)
    .highlight_style(selected_style)
    .highlight_symbol(Text::from(vec![bar.into()]))
    .bg(app.colors.buffer_bg)
    .highlight_spacing(HighlightSpacing::Always);
    f.render_stateful_widget(t, area, &mut app.state);
}

fn render_footer(f: &mut Frame, app: &App, area: Rect) {
    let mut text = if app.ip.edit || app.edit.edit {
        "(Esc) return to top | (→|←) move cursor | (Enter) confirm".to_owned()
    } else if app.script.edit {
        "(Esc) return to top | (→|←) move cursor| (↑) move up | (↓) move down | (Enter) confirm".to_owned()
    } else if app.log.edit {
        "(Esc) return to top | (↑) move up | (↓) move down".to_owned()
    } else {
        "(Esc) quit | (↑) move up | (↓) move down | (→) next page | (←) previous page | (r) read | (u) update | (f) flush | (v) view log | (c) connect | (l) use custom commands | (Enter) edit".to_owned()
    };
    let text_t = format!(
        "\nip:{} | script:{}",
        app.ip.data.clone(),
        app.script.data.clone()
    );
    text += &text_t;
    let info_footer = Paragraph::new(text)
        .style(Style::new().fg(app.colors.row_fg).bg(app.colors.buffer_bg))
        .centered()
        .block(
            Block::bordered()
                .border_type(BorderType::Double)
                .border_style(Style::new().fg(app.colors.footer_border_color)),
        );
    f.render_widget(info_footer, area);
}

#[inline]
fn v_half_rect(percent_x: u16, percent_y1: u16, percent_y2: u16, r: Rect) -> (Rect, Rect) {
    let popup_layout = Layout::horizontal([
        Constraint::Percentage((100 - percent_x) / 2),
        Constraint::Percentage(percent_x / 2),
        Constraint::Percentage(percent_x / 2),
        Constraint::Percentage((100 - percent_x) / 2),
    ]).split(r);
    let popup_layout_halfs1 = Layout::vertical([
        Constraint::Percentage((100 - percent_y1) / 2),
        Constraint::Percentage(percent_y1),
        Constraint::Percentage((100 - percent_y1) / 2),
    ]).split(popup_layout[1])[1];
    let popup_layout_halfs2 = Layout::vertical([
        Constraint::Percentage((100 - percent_y2) / 2),
        Constraint::Percentage(percent_y2),
        Constraint::Percentage((100 - percent_y2) / 2),
    ]).split(popup_layout[2])[1];
    (popup_layout_halfs1, popup_layout_halfs2)
}

#[inline]
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
    let popup_layout = Layout::vertical([
        Constraint::Percentage((100 - percent_y) / 2),
        Constraint::Percentage(percent_y),
        Constraint::Percentage((100 - percent_y) / 2),
    ])
    .split(r);

    Layout::horizontal([
        Constraint::Percentage((100 - percent_x) / 2),
        Constraint::Percentage(percent_x),
        Constraint::Percentage((100 - percent_x) / 2),
    ])
    .split(popup_layout[1])[1]
}

#[inline]
fn render_log(app: &mut App, f: &mut Frame, area_script_log: &Rect) {
    let print_str: &Vec<String> = &app.log.data;
    let print_str = print_str.iter().fold(String::from(""), |x, y| x + y);
    let block_log = Paragraph::new(print_str)
        .wrap(Wrap { trim: true })
        .style(Style::new().fg(app.colors.row_fg).bg(app.colors.buffer_bg))
        .block(
            Block::bordered()
                .border_type(BorderType::Double)
                .border_style(Style::new().fg(app.colors.footer_border_color)),
        )
        .scroll((app.log_vertical_scroll as u16, 0));
    f.render_widget(Clear, *area_script_log);
    f.render_widget(block_log, *area_script_log);
    f.render_stateful_widget(
        Scrollbar::new(ScrollbarOrientation::VerticalRight)
            .begin_symbol(Some("↑"))
            .end_symbol(Some("↓")),
        *area_script_log,
        &mut app.log_vertical_scroll_state,
    )
}

async fn tcp_top(tx: Sender<Signal>, mut rx: Receiver<Signal>, periodic_update: bool) {
    let mut stream: Option<TcpStream> = None;
    let mut ip = None;
    let mut buf = vec![0; 16384];
    let mut point = 0;
    loop {
        let mut periodic_update_base = 0;
        if let Some(stream_i) = &mut stream {
            select! {
                signal = rx.recv() => {
                    if let Some(signal) = signal {
                        match signal {
                            Signal::CONNECT(data) => {
                                if let Ok(s) = TcpStream::connect(&data).await {
                                    stream = Some(s);
                                    info!("ip: {} connected", &data);
                                } else {
                                    stream = None;
                                    warn!("ip: {} refuse to connect", &data);
                                }
                                ip = Some(data);
                            }
                            Signal::WRITE(addr, data, check) => {
                                let extra = if check {
                                    1
                                } else {
                                    0
                                };
                                let pack = Packet {
                                    cmd: "write".to_owned(),
                                    status: "ok".to_owned(),
                                    payload: vec![addr, data, extra],
                                };
                                let b = serde_json::to_string(&pack).unwrap();
                                let b = b.as_bytes();
                                if let Ok(x) = stream_i.write(b).await {
                                    info!("ip: {} send {}", ip.clone().unwrap(), x);
                                } else {
                                    warn!("ip: {} send failed", ip.clone().unwrap());
                                }
                            }
                            Signal::FLUSH => {
                                let mut pack = Packet {
                                    cmd: "write".to_owned(),
                                    status: "ok".to_owned(),
                                    payload: vec![31, 0, 0],
                                };
                                let mut a = serde_json::to_string(&pack).unwrap();
                                pack.payload[1] = 255;
                                let b = serde_json::to_string(&pack).unwrap();
                                a += &b;
                                pack.payload[1] = 0;
                                let b = serde_json::to_string(&pack).unwrap();
                                a += &b;
                                let b = a.as_bytes();
                                if let Ok(x) = stream_i.write(b).await {
                                    info!("ip: {} send {}", ip.clone().unwrap(), x);
                                } else {
                                    warn!("ip: {} send failed", ip.clone().unwrap());
                                }
                            }
                            Signal::SCRIPT(data) => {
                                let t :Vec<u8> = data.split_ascii_whitespace().map(|x| {
                                    if let Ok(t) = x.parse() {
                                        t
                                    } else {
                                        0u8
                                    }
                                }).collect();
                                if t.len() % 3 == 0{
                                    let pack = Packet {
                                        cmd: "script".to_owned(),
                                        status: "ok".to_owned(),
                                        payload: t,
                                    };
                                    let b = serde_json::to_string(&pack).unwrap();
                                    let b = b.as_bytes();
                                    if let Ok(x) = stream_i.write(b).await {
                                        info!("ip: {} send {}", ip.clone().unwrap(), x);
                                    } else {
                                        warn!("ip: {} send failed", ip.clone().unwrap());
                                    }
                                }
                            }
                            Signal::WRITEBITS(bits, data) => {
                                let pack = PacketBits {
                                    cmd: "rcmd".to_owned(),
                                    status: "ok".to_owned(),
                                    bits,
                                    data,
                                };
                                let b = serde_json::to_string(&pack).unwrap();
                                let b = b.as_bytes();
                                if let Ok(x) = stream_i.write(b).await {
                                    info!("ip: {} send {}", ip.clone().unwrap(), x);
                                } else {
                                    warn!("ip: {} send failed", ip.clone().unwrap());
                                }
                            }
                            Signal::RESET => {
                                let pack = PacketStr {
                                    cmd: "reset".to_owned(),
                                    status: "ok".to_owned(),
                                    payload: "".to_owned(),
                                };
                                let b = serde_json::to_string(&pack).unwrap();
                                let b = b.as_bytes();
                                if let Ok(x) = stream_i.write(b).await {
                                    info!("ip: {} send {}", ip.clone().unwrap(), x);
                                } else {
                                    warn!("ip: {} send failed", ip.clone().unwrap());
                                }
                            }
                            Signal::RCMD(data) => {
                                let pack = PacketStr {
                                    cmd: "rcmd".to_owned(),
                                    status: "ok".to_owned(),
                                    payload: data,
                                };
                                let b = serde_json::to_string(&pack).unwrap();
                                let b = b.as_bytes();
                                if let Ok(x) = stream_i.write(b).await {
                                    info!("ip: {} send {}", ip.clone().unwrap(), x);
                                } else {
                                    warn!("ip: {} send failed", ip.clone().unwrap());
                                }
                            }
                            Signal::READ(addr) => {
                                let pack = Packet {
                                    cmd: "read".to_owned(),
                                    status: "ok".to_owned(),
                                    payload: vec![addr],
                                };
                                let b = serde_json::to_string(&pack).unwrap();
                                let b = b.as_bytes();
                                if let Ok(x) = stream_i.write(b).await {
                                    info!("ip: {} send {}", ip.clone().unwrap(), x);
                                } else {
                                    warn!("ip: {} send failed", ip.clone().unwrap());
                                }
                            }
                            _ => {info!("Signal received");}
                        }
                    }
                }
                signal = stream_i.read(&mut buf[point..16384]) => {
                    let n = signal.expect("failed to read data from socket");
                    point += n;
                    if point == 16384 {
                        warn!("Overflow");
                        return
                    }
                    loop {
                        let mut flag = false;
                        let mut endpoint = 0;
                        let mut stack_count = 0;
                        for i in 0..point {
                            if flag {
                                if buf[i] == '{' as u8 {
                                    stack_count += 1;
                                } else if buf[i] == '}' as u8 {
                                    stack_count -= 1;
                                }
                                if stack_count == 0 {
                                    endpoint = i + 1;
                                    break;
                                }
                            } else {
                                if buf[i] == '{' as u8 {
                                    stack_count = 1;
                                    flag = true;
                                }
                            }
                        }
                        if (stack_count == 0) && flag {
                            let data: Vec<u8> = Vec::from_iter(buf[0..endpoint].iter().map(|x| *x));
                            for i in endpoint..point {
                                buf[i - endpoint] = buf[i];
                            }
                            point -= endpoint;
                            let data: String = String::from_utf8(data).unwrap();
                            info!("Recv:\n{}", &data);
                            let parsed: Packet = serde_json::from_str(&data).unwrap();
                            match parsed.cmd.clone().as_str() {
                                "alive_detection" => {
                                    let pack = Packet {
                                        cmd: "idle".to_owned(),
                                        status: "ok".to_owned(),
                                        payload: vec![],
                                    };
                                    let b = serde_json::to_string(&pack).unwrap();
                                    let b = b.as_bytes();
                                    if let Ok(x) = stream_i.write(b).await {
                                        info!("ip: {} send {}", ip.clone().unwrap(), x);
                                    } else {
                                        warn!("ip: {} send failed", ip.clone().unwrap());
                                    }
                                },
                                "read_reply" => {
                                    let b = Signal::READREPLY(parsed.payload[0], parsed.payload[1]);
                                    tx.send(b).await.expect("mpsc write failed!");
                                },
                                "read_reply_ov" => {
                                    let b = Signal::READREPLYOV(parsed.payload[0], parsed.payload[1]);
                                    tx.send(b).await.expect("mpsc write failed!");
                                },
                                _ => {}
                            }
                        }
                        if !flag {
                            break;
                        }
                    }
                }
                _ = sleep(Duration::from_secs(1)) => {
                    if periodic_update {
                        let mut pack_str = String::new();
                        for i in 0..7 {
                            let pack = Packet {
                                cmd: "read".to_owned(),
                                status: "ok".to_owned(),
                                payload: vec![i + periodic_update_base],
                            };
                            let b = serde_json::to_string(&pack).unwrap();
                            pack_str += &b;
                        }
                        let pack = pack_str.as_bytes();
                        if let Ok(x) = stream_i.write(pack).await {
                            trace!("ip: {} send {}", ip.clone().unwrap(), x);
                        } else {
                            warn!("ip: {} send failed", ip.clone().unwrap());
                        }
                        periodic_update_base = (periodic_update_base + 8) % 32;
                    }
                }
            }
        } else {
            if let Some(signal) = rx.recv().await {
                match signal {
                    Signal::CONNECT(data) => {
                        if let Ok(s) = TcpStream::connect(&data).await {
                            stream = Some(s);
                            info!("ip: {} connected", &data);
                        } else {
                            stream = None;
                            warn!("ip: {} refuse to connect", &data);
                        }
                        ip = Some(data);
                    }
                    _ => info!("Signal recv"),
                }
            }
        }
    }
}
